README

Path: vendor/plugins/simply_presentable/README
Last Update: Tue Jan 16 12:25:18 -0800 2007

SimplyPresentable

Save keystrokes and stay DRY with SimplyPresentable:

  <% present(foo).form do |f| %>
    <%= f.text_field :bar %>
  <% end %>

Instead of

  <% form_for(
       foo.new_record? ? {
         :url => 'http://example.com/foos',
         :html => { :method => 'post', :id => 'new_foo', :class => 'new_foo' }
       } :
       {
          :url => "http://example.com/foos/#{foo.id}",
          :html => { :method => 'put', :id => 'foo_1', :class => 'foo' }
        } :
      ) do |f| %>
    <%= f.text_field :bar %>
  <% end %>

SimplyPresentable is a plugin that introduces the idea of presenters to rails:

martinfowler.com/eaaDev/PresentationModel.html

Rails helpers are functional, and organized around controllers, not domain objects. Helper functions that are intended to operate on data types that span controllers end up being in the ApplicationHelper, or packaged into modules and included in other helpers.

SimplyPresentable attempts to fill the OO void left by a functional helper only approach to presentation:

  <%= link_to 'Show Foo', present(@foo).url %>

In this example, the present method (added to ActionController::Base and ActionView::Base) is called on @foo, and instance of a Foo object. When present is called:

  1. The appropriate presenter(s) is located for foo based on its class
  2. The public methods of the presenters are delegated to a presenter proxy
  3. All other messages are delegated to the foo object
  4. The presenter proxy object is returned

Some might consider this a violation of MVC, since the interfaces of the presenters and the domain object are exposed by the presenter proxy. However, MVC is meant to separate IMPLEMENTATION, not interface. You would never want the implementation of a domain model class to depend on the way in which it will be displayed. SimplyPresentable avoids this, by defining all presentation logic in a separate class. The interfaces of the presenter classes and domain models are all exposed through the presenter proxy as a matter of convenience.

Installation

script/plugin install svn://richcollins.net/svn/simply_presentable

Running Tests

rake test:plugins

Code Coverage

RCOV script/plugin install svn.codahale.com/rails_rcov rake test:plugins:rcov

(SimplyPresentable has also been Heckled)

Documentation

simply_presentable.richcollins.net/

Core Classes

The presenters included in the plugin are mainly oriented around the ActiveRecord::Base class. There are many instances where information about domain objects is used to control a RESTful rails application. SimplyPresentable is strongly influenced by the idea of convention over configuration. This philosophical choice is apparent in the three core presenters.

SimplyPresentable::ActiveRecordUrlPresenter

SimplyPresentable::ActiveRecordUrlPresenter makes it easy to generate urls based on ActiveRecord::Base instances and named routes. For instance:

routes.rb:

  map.resources :foos do |foo|
    map.resources :bars
  end

in the template / controller:

  >> present(bar).url
  => http://yourdomain.com/foos/1/bars/2

Compare to named url functions:

  >> bar_url(:id => bar, :bar_id => bar.foo)

This assumes a few things:

  1. bar has a method named foo that will return the foo associated with bar (belongs_to association most likely)
  2. bar has an id of 2
  3. foo as an id of 1

The url helper also includes the methods new_url, edit_url and collection_url.

There are additional options available for name_prefixes, actions and other url customization features. See the documentation for SimplyPresentable::ActiveRecordUrlPresenter.

The SimplyPresentable::ActiveRecordUrlPresenter interface is used by:

SimplyPresentable::ActiveRecordPartialPresenter

SimplyPresentable::ActiveRecordPartialPresenter makes it easier to render partials based on an ActiveRecord::Base instance:

  present(Foo.new).render

This will render foos/_new.rhtml

  present(Foo.find(:first)).render

This will render foos/_show.rhtml

Compare to render method:

  <%= render(:partial => 'foos/' + foo.new_record? ? 'new' : 'show', :locals => { :foo => foo }) %>

You can also change the partial:

  present(foo).render(:existing => 'edit')

OR

  present(foo).render(:partial => 'edit')

This will render foos/_edit.rhtml

You can also get partial options to be used elsewhere:

  present(Foo.find(:first)).partial_options
  => { :partial => 'foos/show', :foo => the_foo_instance }

The SimplyPresentable::ActiveRecordPartialPresenter interface is used by:

SimplyPresentable::ActiveRecordDomPresenter

SimplyPresentable::ActiveRecordDomPresenter makes it easy to name and control dom and css elements based on an ActiveRecord::Base instance:

  present(Foo.new).dom_id
  => new_foo

  present(Foo.find(:first)).dom_id
  => foo_1

  present(Foo.find(:first)).dom_class
  => foo

The SimplyPresentable::ActiveRecordPartialPresenter interface is used by:

SimplyPresentable::ArrayPartialPresenter

SimplyPresentable::ArrayPartialPresenter makes it easier to render partials based on an Array instance:

  present(Foo.new, Foo.find(:first)).render_each(:spacer_template => 'foos/between')

This will call render on each Foo instance. The spacer template will be rendered and the results obtained by rendering the Foos will be joined with the spacer template

  present(Foo.new, Foo.find(:first)).render_as('foos')

This will render the template foos/index with the array bound to the variable foos.

Extensibility

The plugin expects a directory, RAILS_ROOT/app/presenters that will contain application specific presenters. These presenters will be loaded on each request during development mode. All presenters must inherit from SimplyPresentable::Presenter

You can declare which classes the presenter should be used with:

  used_for :foo

This will expose the public methods of the presenter through the PresenterProxy object (or any of its subclasses) that is returned by the present method when a Foo object is passed as an argument.

You can also tell the presenter to skip a class:

  not_used_for :bar

This is useful when you have a subclass that should use a different (or no) presenter:

  class Foo
  end

  class Bar < Foo
  end

  class FooPresenter < SimplyPresentable::Presenter
    used_for :foo
    not_used_for :bar
  end

If there is a "presenter collision", present will throw an Exception:

  class FooPresenter < SimplyPresentable::Presenter
    used_for :foo
  end

  class AnotherFooPresenter < SimplyPresentable::Presenter
    used_for :foo
  end

If presenters share the same inheritance lineage, the sub classes method will be used as expected.

Credits

Adam Thorsen helped me extensively with testing and other feedback.

This plugin was inspired by its functional counterpart, simply_helpful:

dev.rubyonrails.org/browser/plugins/simply_helpful

Contact

richcollins@gmail.com richcollins.net/

[Validate]